package com.moli.lumpSugar.user.service.impl;

import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.core.type.TypeReference;

import com.moli.lumpSugar.common.domain.vo.response.ApiResult;
import com.moli.lumpSugar.common.utils.JsonUtils;
import com.moli.lumpSugar.user.dao.UserDao;
import com.moli.lumpSugar.user.domain.entity.IpDetail;
import com.moli.lumpSugar.user.domain.entity.IpInfo;
import com.moli.lumpSugar.user.domain.entity.User;
import com.moli.lumpSugar.user.service.IpService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class IpServiceImpl implements IpService,AutoCloseable {
    /**
     * 使用自定义线程池
     * 初始化一个单线程的ExecutorService用于执行异步任务
     * 该线程池用于处理IP详情的刷新任务，确保这些任务在一个独立的线程中执行，避免影响主线程
     * 使用单线程的线程池可以保证任务的顺序性，这对于需要按顺序处理的任务（如IP详情的刷新）非常重要
     * 同时，通过设置线程的名称，提高了日志的可读性和问题的可追踪性
     */
    /*
    * ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   ThreadFactory threadFactory)
      corePoolSize 设置了线程池的 核心线程数。核心线程数指线程池保持的最小线程数量，即使这些线程处于空闲状态，也不会被回收。
      * maximumPoolSize 设置了线程池的 最大线程数。最大线程数决定线程池可以同时容纳多少个线程。
      * keepAliveTime 设置了非核心线程在空闲时保持存活的时间。这里设置为 0L，意味着当线程池中的线程空闲时，线程会立即被回收。
      * 由于 corePoolSize 和 maximumPoolSize 都是 1，实际上这个设置对当前配置没有太大影响，因为线程池始终维持一个线程，并且空闲时不会被回收。
      * TimeUnit.MILLISECONDS 表示 keepAliveTime 的时间单位是 毫秒。这决定了空闲线程的存活时间单位。
      * workQueue 是一个 阻塞队列，用于存放待执行的任务。这里使用了 LinkedBlockingQueue，它是一个基于链表的阻塞队列，允许队列中有最多 500 个任务（500 是队列的容量）。
      * LinkedBlockingQueue 会在队列满时阻塞，直到有任务被执行并从队列中移除。
      * threadFactory 用来为线程池创建新的线程。这里使用了一个自定义的线程工厂 NamedThreadFactory，它的作用是为线程池中的线程指定一个名称前缀，便于线程的跟踪和管理。
    *
    * */
    private static ExecutorService executor = new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(500), new NamedThreadFactory("refresh-ipDetail", false));

    @Autowired
    private UserDao userDao;

    @Override
    public void refreshIpDetailAsync(Long uid) {
        executor.execute(() -> {
            User user = userDao.getById(uid);
            IpInfo ipInfo = user.getIpInfo();
            if (Objects.isNull(ipInfo)) {
                return;
            }
            String ip = ipInfo.needRefreshIP();
            if (StringUtils.isBlank(ip)) {
                return;
            }
            IpDetail ipDetail = TryGetIpDetailOrNullTreeTimes(ip);
            if (Objects.nonNull(ipDetail)) {
                ipInfo.refreshIpDetail(ipDetail);
            }
            User updateIpInfoUser = new User();
            updateIpInfoUser = updateIpInfoUser.builder()
                    .id(user.getId())
                    .ipInfo(ipInfo)
                    .build();
            userDao.updateById(updateIpInfoUser);
        });
    }

    private static IpDetail TryGetIpDetailOrNullTreeTimes(String ip) {
        for (int i = 0; i < 3; i++) {
            IpDetail ipDetail = getIpDetailOrNull(ip);
            if (Objects.nonNull(ipDetail)) {
                return ipDetail;
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                log.error("TryGetIpDetailOrNullTreeTimes InterruptedException", e);
            }
        }
        return null;
    }

    public static IpDetail getIpDetailOrNull(String ip) {
        String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc");
        ApiResult<IpDetail> result = JsonUtils.toObj(body, new TypeReference<ApiResult<IpDetail>>() {
        });
        if (Objects.nonNull(result.getData())) {
            return result.getData();
        }
        return null;
    }

    @Override
    public void close() {
        shutdownExecutor();
    }

    public void shutdownExecutor() {
        executor.shutdown(); // 拒绝新的任务
        try {
            // 等待现有任务完成
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow(); // 强制关闭
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
